using System;
using System.IO;
using System.Text;
using Velocity4Net.Util.Introspection;
using Velocity4Net.Runtime.Parse.AST;
using Velocity4Net.Ctx;
using Velocity4Net.Errors;
namespace Velocity4Net.Util
{
    /**
     * Simple utility functions for manipulating classes and resources
     * from the classloader.
     *
     *  @author <a href="mailto:wglass@apache.org">Will Glass-Husain</a>
     *  @version $Id: ClassUtils.java 898032 2010-01-11 19:51:03Z nbubna $
     * @since 1.5
     */
    public class ClassUtils
    {

        /**
         * Utility class; cannot be instantiated.
         */
        private ClassUtils()
        {
        }

        /**
         * Return the specified class.  Checks the ThreadContext classloader first,
         * then uses the System classloader.  Should replace all calls to
         * <code>Class.forName( claz )</code> (which only calls the System class
         * loader) when the class might be in a different classloader (e.g. in a
         * webapp).
         *
         * @param clazz the name of the class to instantiate
         * @return the requested Class object
         * @throws ClassNotFoundException
         */
        public static Type getClass(String clazz)
        {
            /**
             * Use the Thread context classloader if possible
             */
            //ClassLoader loader = Thread.currentThread().getContextClassLoader();
            //if (loader != null)
            //{
            //    try
            //    {
            //        return Class.forName(clazz, true, loader);
            //    }
            //    catch (ClassNotFoundException E)
            //    {
            //        /**
            //         * If not found with ThreadContext loader, fall thru to
            //         * try System classloader below (works around bug in ant).
            //         */
            //    }
            //}
            /**
             * Thread context classloader isn't working out, so use system loader.
             */
            return Type.GetType(clazz);
        }

        /**
         * Return a new instance of the given class.  Checks the ThreadContext
         * classloader first, then uses the System classloader.  Should replace all
         * calls to <code>Class.forName( claz ).newInstance()</code> (which only
         * calls the System class loader) when the class might be in a different
         * classloader (e.g. in a webapp).
         *
         * @param clazz the name of the class to instantiate
         * @return an instance of the specified class
         * @throws ClassNotFoundException
         * @throws IllegalAccessException
         * @throws InstantiationException
         */
        public static Object getNewInstance(String clazz)
        {
            return Type.GetType(clazz).Assembly.CreateInstance(clazz);
        }

        /**
         * Finds a resource with the given name.  Checks the Thread Context
         * classloader, then uses the System classloader.  Should replace all
         * calls to <code>Class.getResourceAsString</code> when the resource
         * might come from a different classloader.  (e.g. a webapp).
         * @param claz Class to use when getting the System classloader (used if no Thread
         * Context classloader available or fails to get resource).
         * @param name name of the resource
         * @return InputStream for the resource.
         */
        public static TextReader getResourceAsStream(Type claz, String name)
        {
            TextReader result = null;

            /**
             * remove leading slash so path will work with classes in a JAR file
             */
            while (name.StartsWith("/"))
            {
                name = name.Substring(1);
            }

            //ClassLoader classLoader = Thread.currentThread()
            //                            .getContextClassLoader();

            //if (classLoader == null)
            //{
            //    classLoader = claz.getClassLoader();
            //    result = classLoader.getResourceAsStream( name );
            //}
            //else
            //{
            //    result= classLoader.getResourceAsStream( name );

            //    /**
            //    * for compatibility with texen / ant tasks, fall back to
            //    * old method when resource is not found.
            //    */

            //    if (result == null)
            //    {
            //        classLoader = claz.getClassLoader();
            //        if (classLoader != null)
            //            result = classLoader.getResourceAsStream( name );
            //    }
            //}

            return result;

        }

        /**
         * Lookup a VelMethod object given the method signature that is specified in
         * the passed in parameters.  This method first searches the cache, if not found in
         * the cache then uses reflections to inspect Object o, for the given method.
         * @param methodName Name of method
         * @param params Array of objects that are parameters to the method
         * @param paramClasses Array of Classes coresponding to the types in params.
         * @param o Object to introspect for the given method.
         * @param context Context from which the method cache is aquirred
         * @param node ASTNode, used for error reporting.
         * @param strictRef If no method is found, throw an exception, never return null in this case
         * @return VelMethod object if the object is found, null if not matching method is found
         */
        public static VelMethod getMethod(String methodName, Object[] args,
                                          Type[] paramClasses, Object o, InternalContextAdapter context,
                                          SimpleNode node, bool strictRef)
        {
            VelMethod method = null;
            try
            {
                /*
                 * check the cache
                 */
                ASTMethod.MethodCacheKey mck = new ASTMethod.MethodCacheKey(methodName, paramClasses);
                IntrospectionCacheData icd = context.ICacheGet(mck);

                /*
                 * like ASTIdentifier, if we have cache information, and the Class of
                 * Object o is the same as that in the cache, we are safe.
                 */
                if (icd != null && (o != null && icd.contextData == o.GetType()))
                {

                    /*
                     * get the method from the cache
                     */
                    method = (VelMethod)icd.thingy;
                }
                else
                {
                    /*
                     * otherwise, do the introspection, and then cache it
                     */
                    method = node.RuntimeServices.getUberspect().getMethod(o, methodName, args,
                       new Info(node.TemplateName, node.Line, node.Column));

                    if ((method != null) && (o != null))
                    {
                        icd = new IntrospectionCacheData();
                        icd.contextData = o.GetType();
                        icd.thingy = method;

                        context.ICachePut(mck, icd);
                    }
                }

                /*
                 * if we still haven't gotten the method, either we are calling a method
                 * that doesn't exist (which is fine...) or I screwed it up.
                 */
                if (method == null)
                {
                    if (strictRef)
                    {
                        // Create a parameter list for the exception error message
                        StringBuilder plist = new StringBuilder();
                        for (int i = 0; i < args.Length; i++)
                        {
                            Type param = paramClasses[i];
                            plist.Append(param == null ? "null" : param.Name);
                            if (i < args.Length - 1)
                                plist.Append(", ");
                        }
                        throw new MethodInvocationException("Object '"
                            + o.GetType().Name + "' does not contain method "
                            + methodName + "(" + plist + ")", null, methodName, node
                             .TemplateName, node.Line, node.Column);
                    }
                    else
                    {
                        return null;
                    }
                }

            }
            catch (MethodInvocationException mie)
            {
                /*
                 * this can come from the doIntrospection(), as the arg values are
                 * evaluated to find the right method signature. We just want to propogate
                 * it here, not do anything fancy
                 */

                throw mie;
            }
            catch (ApplicationException e)
            {
                /**
                 * pass through application level runtime exceptions
                 */
                throw e;
            }
            catch (System.Exception e)
            {
                /*
                 * can come from the doIntropection() also, from Introspector
                 */
                String msg = "ASTMethod.execute() : exception from introspection";
                throw new VelocityException(msg, e);
            }

            return method;
        }

    }
}